/*
 * core.js
 * By Tunde Ashafa <http://ashafa.com/>
 * Copyright 2008 Tunde Ashafa, under the MIT License
 */

var $core = {};

(function(){
	
	$core.controllers = {};
	
	// called during servlet init in the shared scope
	$core.init = function(){
		
		if(!$core.javaDomCache.isRunning()) {
			$core.javaDomCache.addListener(new com.ashafa.cache.DomCacheEventListener({
				// a change in one of the files that creates a viewName has been detected,
				// delete its controller from core and expire cache.
				refresh:function(entry){
					var dom, controllers, files;
					delete $core.controllers[entry.viewName];
					entry.setDocument(null)
					entry.setControllers(null);
				},
				evict:function(entry){}
			}));
			// start the domCache scanner...
			$core.javaDomCache.startFileScanner();
		}
	}
	
	
	
	// called from our servlet doGet in a threaded scope and returns the HTML response string
	$core.getOutput = function(viewName, _request, _response, uriPath, tokens){
			
		
		var controllers, files;
		var request = new Request(_request, _response, uriPath, tokens);
		var response = new Response(_request, _response);
		
				
		// get our docuemt form cache (html string)
		var doc = $core.javaDomCache.getDocument(viewName);
		if(doc){ 
			// setup the controllers fotr this viewName
			controllers = ($core.javaDomCache.getControllers(viewName) + "").split(",");
			// create the dom form the string
			window.document = createDomFromString(doc);
		} else {
			// we don't have this viewName cached. go thru the tedious process of creating the viewName 
			// from the template rules in the controller js.
			[window.document, controllers, files] = createDomFromView(viewName, {}, processView(viewName), [], files);
			if(files && window.document){
				$core.javaDomCache.put(viewName, files, window.document.toXMLString(), controllers.toString());
			}
		}
		
		controllers.forEach(function(v){
			if($core.controllers[v]){
				var appController = $core.controllers[v](request, response);
				if(appController && appController.handler && appController.handler.constructor == Function){
					var handler = new appController.handler(request, response);
					var method = (request.isPost) ? "post" : "get";
					if(handler[method] && handler[method].constructor == Function){
						handler[method]();
					}
				}
			}
		});
		
		if(response.commit()) return;
				
		// if the viewName starts with '~' just return the html body
		var returnStr;
		if(!/^~/.test(viewName)){
			returnStr = window.document.toXMLString();
			if(/^<html/.test(returnStr)){
				returnStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + returnStr;
			} else {
				response.addHeader("Content-Type", "application/xhtml+xml");
				returnStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + window.document.toXMLString();
			}
		} else {
			returnStr =  window.document.getElementsByTagName("body")[0].innerHTML;
		}
		
		return returnStr;
	}
	
	// create a template object that contains all views that make up this viewName
	// while adding their controllers to $core
	var processView = function(v, views){
		views = views || {};
		$core.controllerManager.addController(v);
		if($core.controllers[v] && $core.controllers[v]().view && !$core.controllers[v]().view["xml"]){
			views[v] = $core.controllers[v]().view;
			if(views[v].adhocControllers)
				views[v].adhocControllers.forEach(function(controller){
					processView(controller, views);
				});
			if(views[v].extend){
				processView(views[v].extend, views);
			}
			
		}
		return views;
	}
	
	// create DOM Document from a string
	var createDomFromString = function(str){
		return new DOMDocument(str);
	}
	
	// create DOM Document from a url mapped template view	
	var createDomFromView = function(viewName, modules, views, controllers, files){
			
		if(views[viewName])
		{
			var dom;
			
			// save all the modules of this view if present, we will need them later if we are extending this view 
			if(views[viewName].put){
				$.each(views[viewName].put, function(i, val){
					if(!modules[i]){
						modules[i] = val;
					}
				});
			}
			
			if(views[viewName].adhocControllers && views[viewName].adhocControllers.constructor == Array){
				views[viewName].adhocControllers.forEach(function(controller){
					controllers.unshift(controller);
				});
			}
			
			// if we haven't processed this view yet...
			if(controllers.indexOf(viewName) < 0){
				// ... save the viewName as a controller name
				controllers.unshift(viewName);
				if(views[viewName].extend && viewName != views[viewName].extend){
					[dom, controllers, files] = createDomFromView(views[viewName].extend, modules, views, controllers, files);
				}
			} else {
				// throw an error because it will cause an infinite loop processing a view
				// that extends itself or is called again somewhere down the view chain
				throw "'" + viewName + "' already applied. Recursive view.";
			}
			
			// if we get to this point and we havent created a dom yet then we are ready to create one			
			if(!dom){
				if(!files){
					files = new java.util.ArrayList();
				}
							
				controllers.forEach(function(c){
					files.add($core.servletContextPath + "/controllers/" + c + ".js");
				});
				var viewHtmlFile = views[viewName].use || viewName + ".html";
											
				try{
					dom = new DOMDocument(com.ashafa.fileutils.FetchFile.readFile($core.servletContextPath + "/views-html/" + viewHtmlFile, "xxxx"));
				} catch(e) {
					return [createDomFromString("<html><head><title></title></head><body></body></html>"), controllers, files];
				}
				
				files.add($core.servletContextPath + "/views-html/" + viewHtmlFile);
				
				$.each(modules, function(i, val){
					var element = dom.getElementById(i);
					if(element){
						element.innerHTML = (views[val]) 
							? com.ashafa.fileutils.FetchFile.readFile($core.servletContextPath + "/views-html/" + (views[val].use || val + ".html"), "") + "" 
							: com.ashafa.fileutils.FetchFile.readFile($core.servletContextPath + "/views-html/" + val, "") + "";
						files.add($core.servletContextPath + "/views-html/" + ((views[val]) ? (views[val].use || val+".html") : val));
						if(views[val]){
							files.add($core.servletContextPath + "/controllers/" + val + ".js");
							controllers.push(val);
						}
					}	
				});
			}
			return [dom, controllers, files];
			
		}
		controllers.push(viewName);
		
		var returnString;
		
		if($core.controllers[viewName]().view && $core.controllers[viewName]().view["xml"]){
			var rootXML = $core.controllers[viewName]().view["xml"];
			returnString = "<"+rootXML+" />"
		} else {
			returnString = "<html><head><title></title></head><body></body></html>"
		}
		
		return [createDomFromString(returnString), controllers, files]; 
	}
	
	
	
	var Request = function(request, response, uriPath, tokens){
		
		var encodeOptions = function(options){
			var buf = []
			if (typeof(options) === "object" && options !== null) {
		    	for (var name in options) {
		        	if (!options.hasOwnProperty(name)) continue;
					var value = options[name];
					buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
				}
			}
		    if (!buf.length) {
		    	return "";
		    }
			return "?" + buf.join("&");
		};
		
		this.encodeHTML = function(str) {
			return (str + "").replace(/&/g,'&amp;amp;').replace(/</g,'&amp;lt;').replace(/>/g,'&amp;gt;').replace(/'/g,'&amp;apos;').replace(/"/g,'&amp;quot;');
		};
		var uriObj = {toString: function(){
				return uriPath;
		}};
		
		if(tokens){
			uriObj.tokens = eval('(' + tokens + ')');
		}
		
	
		
		this.uri = uriObj;
		
		this.param = function(p){
			var params = request.getParameterValues(p);
			if(params) {
				if(params.length === 1){
					return  this.encodeHTML(params[0]+"");
				} else if(params.length === 0){
					return null;
				}else {
					var retArray = [];
					for(var i in params){
						retArray[i] = this.encodeHTML(params[i]+"");
					}
					return retArray;
				}
			} else {
				return null;
			}
		};
		
		this.setSessAttr = function(a, v){
			request.getSession().setAttribute(a, v);
		};
		
		this.getSessAttr = function(a){
			return request.getSession().getAttribute(a);
		};
		
		this.setAttr = function(a, v){
			request.setAttribute(a, v);
		};
		
		this.getAttr = function(a){
			return request.getAttribute(a);
		};
		
		this.forward = function(url,obj){
		
			var rd = request.getRequestDispatcher(url + encodeOptions(obj));
			rd.forward(request, response);
		};
		
		this.javaRequest = request;
		
		this.toString = function(){
			return "env Request Object";
		};
		
	}
	
	Request.prototype = {
			
		get method(){
			return this.javaRequest.method+"";
		},
		
		get isGet(){
			return (this.javaRequest.method+"" === "GET");
		},
		
		get isPost(){
			return (this.javaRequest.method+"" === "POST");
		},
		
		get clientAddr(){
			return this.javaRequest.remoteAddr()+""; 
		},
		
		get serverAddr(){
			return this.javaRequest.getLocalAddr()+""; 
		},
		
		get serverProtocol(){
			return this.javaRequest.getProtocol()+""; 
		},
		
		get serverPort(){
			return this.javaRequest.getLocalPort(); 
		},
	
		get paramNames(){
			var retArray = [];
			var elements = this.javaRequest.getParameterNames();
			while (elements.hasMoreElements()){
				retArray.push(elements.nextElement()+"");
			}
			return retArray;
		},
		
		get query(){
			return this.javaRequest.queryString() + "";
		},
		
		get params(){
			var obj = {};
			var that = this;
			this.paramNames.forEach(function(v){
				obj[v] = that.param(v);
			});
			return obj;
		},
		
		get cookies(){
			var obj = {};
			var cookies = this.javaRequest.getCookies();
			
			for each(cookie in cookies){
				obj[cookie.name] = cookie.value+"";
			}
			return obj;
		}
	};
	
	var Response = function(request, response){
		
		var cookieDate = function(d){
			var x = d.toGMTString();
			var p = x.split(' ');
			return [p[0], [p[1], p[2], p[3]].join('-'), p[4], p[5]].join(' ');
		}
		var cookieString = function(c){
			var x = '';
			if(!c.name){ 
				throw new Error('cookie name is required');
			}
			c.value = c.value || '';
			x += (c.name + '=' + escape(c.value));
			
			// expires
			if (c.expires instanceof Date) {
				x += ('; expires='+cookieDate(c.expires));
			}
			if (typeof(c.expires) == 'number') {
				var today = (new Date()).valueOf();
				var d = new Date(today + 86400000*c.expires);
				x += ('; expires='+cookieDate(d));
			}
			
			// domain
			if (c.domain) { x += ('; domain='+c.domain); }
			
			// path
			if (c.path) { x += ('; path='+c.path); }
			
			// secure
			if (c.secure == true) { x += '; secure'; }
			
			return x;
		};
		
		var doCommit = false;
		
		this.setHeader = function(key, value){
			response.setHeader(key, value);
		};
		
		this.addHeader = function(key, value){
			response.addHeader(key, value);
		};
		
		this.setCookie = function(cookieObject){
			alert(cookieString(cookieObject))
			this.addHeader('Set-Cookie', cookieString(cookieObject));
		};
		
			
		this.redirect = function(url){
			response.sendRedirect(url || "");
		};
		
		this.setStatus = function(code){
			response.setStatus(code);
		};
		
		this.write = function(msg){
			response.writer.write(msg);
			doCommit = true;
		};
		
		this.commit = function(){
			if(doCommit){
				response.writer.close();
				return true;
			}
		}
		
		this.deleteCookie =  function(name){
			this.setCookie({
				name: name,
				path: "/",
				expires: 0
			});
		}
		
		this.javaResponse = response;
		
		
	};
	
})();
